// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "protocol_test_driver.h"

#include <fuchsia/device/test/c/fidl.h>
#include <lib/zx/object.h>
#include <stdio.h>
#include <zircon/hw/pci.h>

#include <ddk/binding.h>
#include <ddk/device.h>
#include <ddk/platform-defs.h>
#include <ddktl/protocol/pci.h>

#include "../../common.h"
#include "../../config.h"
#include "../fakes/test_device.h"

ProtocolTestDriver* ProtocolTestDriver::instance_;

TEST_F(PciProtocolTests, TestResetDeviceUnsupported) {
  EXPECT_EQ(pci().ResetDevice(), ZX_ERR_NOT_SUPPORTED);
}

// Do basic reads work in the config header?
TEST_F(PciProtocolTests, ConfigReadHeader) {
  uint16_t rd_val16 = 0;
  ASSERT_OK(pci().ConfigRead16(PCI_CFG_VENDOR_ID, &rd_val16));
  ASSERT_EQ(rd_val16, PCI_TEST_DRIVER_VID);
  ASSERT_OK(pci().ConfigRead16(PCI_CFG_DEVICE_ID, &rd_val16));
  ASSERT_EQ(rd_val16, PCI_TEST_DRIVER_DID);
}

TEST_F(PciProtocolTests, ConfigBounds) {
  uint8_t rd_val8 = 0;
  uint16_t rd_val16 = 0;
  uint32_t rd_val32 = 0;

  // Reads/Writes outside of config space should be invalid.
  ASSERT_EQ(pci().ConfigRead8(PCI_EXT_CONFIG_SIZE, &rd_val8), ZX_ERR_OUT_OF_RANGE);
  ASSERT_EQ(pci().ConfigRead16(PCI_EXT_CONFIG_SIZE, &rd_val16), ZX_ERR_OUT_OF_RANGE);
  ASSERT_EQ(pci().ConfigRead32(PCI_EXT_CONFIG_SIZE, &rd_val32), ZX_ERR_OUT_OF_RANGE);
  ASSERT_EQ(pci().ConfigWrite8(PCI_EXT_CONFIG_SIZE, UINT8_MAX), ZX_ERR_OUT_OF_RANGE);
  ASSERT_EQ(pci().ConfigWrite16(PCI_EXT_CONFIG_SIZE, UINT16_MAX), ZX_ERR_OUT_OF_RANGE);
  ASSERT_EQ(pci().ConfigWrite32(PCI_EXT_CONFIG_SIZE, UINT32_MAX), ZX_ERR_OUT_OF_RANGE);

  // Writes within the config header are not allowed.
  for (uint16_t addr = 0; addr < PCI_CONFIG_HDR_SIZE; addr++) {
    ASSERT_EQ(pci().ConfigWrite8(addr, UINT8_MAX), ZX_ERR_ACCESS_DENIED);
    ASSERT_EQ(pci().ConfigWrite16(addr, UINT16_MAX), ZX_ERR_ACCESS_DENIED);
    ASSERT_EQ(pci().ConfigWrite32(addr, UINT32_MAX), ZX_ERR_ACCESS_DENIED);
  }
}

// A simple offset / pattern for confirming reads and writes.
// Ensuring it never returns 0.
constexpr uint16_t kTestPatternStart = 0x800;
constexpr uint16_t kTestPatternEnd = 0x1000;
constexpr uint8_t TestPatternValue(int address) {
  return static_cast<uint8_t>((address % UINT8_MAX) + 1u);
}

// These pattern tests use ConfigRead/ConfigWrite of all sizes to read and write
// patterns to the back half of the fake device's config space, using the standard
// Pci Protocol methods and the actual device Config object.
TEST_F(PciProtocolTests, ConfigPattern8) {
  uint8_t rd_val = 0;

  // Clear it out. Important if this test runs out of order.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd; addr++) {
    ASSERT_OK(pci().ConfigWrite8(addr, 0));
  }

  // Verify the clear.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd; addr++) {
    ASSERT_OK(pci().ConfigRead8(addr, &rd_val));
    ASSERT_EQ(rd_val, 0);
  }

  // Write the pattern out.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd; addr++) {
    ASSERT_OK(pci().ConfigWrite8(addr, TestPatternValue(addr)));
  }

  // Verify the pattern.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd; addr++) {
    ASSERT_OK(pci().ConfigRead8(addr, &rd_val));
    ASSERT_EQ(rd_val, TestPatternValue(addr));
  }
}

TEST_F(PciProtocolTests, ConfigPattern16) {
  uint16_t rd_val = 0;
  auto PatternValue = [](uint16_t addr) -> uint16_t {
    return static_cast<uint16_t>((TestPatternValue(addr + 1) << 8) | TestPatternValue(addr));
  };

  // Clear it out. Important if this test runs out of order.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd - 1;
       addr = static_cast<uint16_t>(addr + 2)) {
    ASSERT_OK(pci().ConfigWrite16(addr, 0));
  }

  // Verify the clear.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd - 1;
       addr = static_cast<uint16_t>(addr + 2)) {
    ASSERT_OK(pci().ConfigRead16(addr, &rd_val));
    ASSERT_EQ(rd_val, 0);
  }

  // Write the pattern out.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd - 1;
       addr = static_cast<uint16_t>(addr + 2)) {
    ASSERT_OK(pci().ConfigWrite16(addr, PatternValue(addr)));
  }

  // Verify the pattern.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd - 1;
       addr = static_cast<uint16_t>(addr + 2)) {
    ASSERT_OK(pci().ConfigRead16(addr, &rd_val));
    ASSERT_EQ(rd_val, PatternValue(addr));
  }
}

TEST_F(PciProtocolTests, ConfigPattern32) {
  uint32_t rd_val = 0;
  auto PatternValue = [](uint16_t addr) -> uint32_t {
    return static_cast<uint32_t>((TestPatternValue(addr + 3) << 24) |
                                 (TestPatternValue(addr + 2) << 16) |
                                 (TestPatternValue(addr + 1) << 8) | TestPatternValue(addr));
  };

  // Clear it out. Important if this test runs out of order.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd - 3;
       addr = static_cast<uint16_t>(addr + 4)) {
    ASSERT_OK(pci().ConfigWrite32(static_cast<uint16_t>(addr), 0));
  }

  // Verify the clear.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd - 3;
       addr = static_cast<uint16_t>(addr + 4)) {
    ASSERT_OK(pci().ConfigRead32(addr, &rd_val));
    ASSERT_EQ(rd_val, 0);
  }

  // Write the pattern out.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd - 3;
       addr = static_cast<uint16_t>(addr + 4)) {
    ASSERT_OK(pci().ConfigWrite32(addr, PatternValue(addr)));
  }

  // Verify the pattern.
  for (uint16_t addr = kTestPatternStart; addr < kTestPatternEnd - 3;
       addr = static_cast<uint16_t>(addr + 4)) {
    ASSERT_OK(pci().ConfigRead32(addr, &rd_val));
    ASSERT_EQ(rd_val, PatternValue(addr));
  }
}

TEST_F(PciProtocolTests, EnableBusMaster) {
  struct pci::config::Command cmd_reg = {};
  uint16_t cached_value = 0;

  // Ensure Bus mastering is already enabled in our test quadro.
  ASSERT_OK(pci().ConfigRead16(PCI_CONFIG_COMMAND, &cmd_reg.value));
  ASSERT_EQ(true, cmd_reg.bus_master());
  cached_value = cmd_reg.value;  // cache so we can test other bits are preserved

  // Ensure we can disable it.
  ASSERT_OK(pci().EnableBusMaster(false));
  ASSERT_OK(pci().ConfigRead16(PCI_CONFIG_COMMAND, &cmd_reg.value));
  ASSERT_EQ(false, cmd_reg.bus_master());
  ASSERT_EQ(cached_value & ~PCI_COMMAND_BUS_MASTER_EN, cmd_reg.value);

  // Enable and confirm it.
  ASSERT_OK(pci().EnableBusMaster(true));
  ASSERT_OK(pci().ConfigRead16(PCI_CONFIG_COMMAND, &cmd_reg.value));
  ASSERT_EQ(true, cmd_reg.bus_master());
  ASSERT_EQ(cached_value, cmd_reg.value);
}

TEST_F(PciProtocolTests, GetBarArgumentCheck) {
  zx_pci_bar_t info = {};
  // Test that only valid BAR ids are accepted.
  ASSERT_EQ(ZX_ERR_INVALID_ARGS, pci().GetBar(PCI_MAX_BAR_REGS, &info));
}

// These individual BAR tests are coupled closely to the device configuration
// stored in test_device.h. If that configuration is changed in a way that
// affects the expected BAR information then these tests also need to be
// updated.
TEST_F(PciProtocolTests, GetBar0) {
  zx_pci_bar_t info = {};
  zx::vmo vmo;
  size_t size;

  // BAR 0 (32-bit mmio, non-pf, size 16M)
  ASSERT_OK(pci().GetBar(0, &info));
  ASSERT_EQ(info.id, 0);
  ASSERT_EQ(info.type, ZX_PCI_BAR_TYPE_MMIO);
  vmo.reset(info.handle);
  vmo.get_size(&size);
  ASSERT_EQ(size, kTestDeviceBars[0].size);
}

TEST_F(PciProtocolTests, GetBar1) {
  zx_pci_bar_t info = {};
  zx::vmo vmo;
  size_t size;

  // BAR 1 (32-bit mmio, pf, size 256M)
  ASSERT_OK(pci().GetBar(1, &info));
  ASSERT_EQ(info.id, 1);
  ASSERT_EQ(info.type, ZX_PCI_BAR_TYPE_MMIO);
  vmo.reset(info.handle);
  vmo.get_size(&size);
  ASSERT_EQ(size, kTestDeviceBars[1].size);
}

TEST_F(PciProtocolTests, GetBar2) {
  zx_pci_bar_t info = {};
  // BAR 2 contains MSI-X registers and should be denied
  ASSERT_EQ(ZX_ERR_ACCESS_DENIED, pci().GetBar(2, &info));
}

TEST_F(PciProtocolTests, GetBar3) {
  zx_pci_bar_t info = {};
  zx::vmo vmo;
  size_t size;

  // BAR 3 (64-bit mmio, pf, size 32M)
  ASSERT_OK(pci().GetBar(3, &info));
  ASSERT_EQ(info.id, 3);
  ASSERT_EQ(info.type, ZX_PCI_BAR_TYPE_MMIO);
  vmo.reset(info.handle);
  vmo.get_size(&size);
  ASSERT_EQ(size, kTestDeviceBars[3].size);
}

TEST_F(PciProtocolTests, GetBar4) {
  zx_pci_bar_t info = {};
  // BAR 4 (Bar 3 second half, should be NOT_FOUND)
  ASSERT_EQ(ZX_ERR_NOT_FOUND, pci().GetBar(4, &info));
}

TEST_F(PciProtocolTests, GetBar5) {
  zx_pci_bar_t info = {};
  // BAR 5 (I/O ports @ 0x2000, size 128)
#if __x86_64__
  ASSERT_OK(pci().GetBar(5, &info));
  ASSERT_EQ(info.type, ZX_PCI_BAR_TYPE_PIO);
  ASSERT_EQ(info.id, 5);
  ASSERT_EQ(info.addr, kTestDeviceBars[5].address);
  ASSERT_EQ(info.size, kTestDeviceBars[5].size);
#else
  ASSERT_EQ(ZX_ERR_NOT_SUPPORTED, pci().GetBar(5, &info));
#endif
}

TEST_F(PciProtocolTests, GetCapabilities) {
  uint8_t offsetA = 0;
  uint8_t offsetB = 0;
  uint8_t val8 = 0;

  // First Power Management Capability is at 0x60.
  ASSERT_OK(pci().GetFirstCapability(PCI_CAP_ID_PCI_PWR_MGMT, &offsetA));
  ASSERT_EQ(0x60, offsetA);
  ASSERT_OK(pci().ConfigRead8(offsetA, &val8));
  ASSERT_EQ(PCI_CAP_ID_PCI_PWR_MGMT, val8);

  // Second Power Management Capability is at 0xA0.
  ASSERT_OK(pci().GetNextCapability(PCI_CAP_ID_PCI_PWR_MGMT, offsetA, &offsetB));
  ASSERT_EQ(0xA0, offsetB);
  ASSERT_OK(pci().ConfigRead8(offsetB, &val8));
  ASSERT_EQ(PCI_CAP_ID_PCI_PWR_MGMT, val8);

  // There is no third Power Management Capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND, pci().GetNextCapability(PCI_CAP_ID_PCI_PWR_MGMT, offsetB, &offsetA));

  // First Pci Express Capability is at 0x78.
  ASSERT_OK(pci().GetFirstCapability(PCI_CAP_ID_PCI_EXPRESS, &offsetA));
  ASSERT_EQ(0x78, offsetA);
  ASSERT_OK(pci().ConfigRead8(offsetA, &val8));
  ASSERT_EQ(PCI_CAP_ID_PCI_EXPRESS, val8);

  // There is no second Pci Express Capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND, pci().GetNextCapability(PCI_CAP_ID_PCI_EXPRESS, offsetA, &offsetB));

  // First MSI Capability is at 0x68.
  ASSERT_OK(pci().GetFirstCapability(PCI_CAP_ID_MSI, &offsetA));
  ASSERT_EQ(0x68, offsetA);
  ASSERT_OK(pci().ConfigRead8(offsetA, &val8));
  ASSERT_EQ(PCI_CAP_ID_MSI, val8);

  // There is no second MSI Capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND, pci().GetNextCapability(PCI_CAP_ID_MSI, offsetA, &offsetB));

  // First Vendor Capability is at 0xC4.
  ASSERT_OK(pci().GetFirstCapability(PCI_CAP_ID_VENDOR, &offsetA));
  ASSERT_EQ(0xC4, offsetA);
  ASSERT_OK(pci().ConfigRead8(offsetA, &val8));
  ASSERT_EQ(PCI_CAP_ID_VENDOR, val8);

  // Second Vendor Capability is at 0xC8.
  ASSERT_OK(pci().GetNextCapability(PCI_CAP_ID_VENDOR, offsetA, &offsetB));
  ASSERT_EQ(0xC8, offsetB);
  ASSERT_OK(pci().ConfigRead8(offsetB, &val8));
  ASSERT_EQ(PCI_CAP_ID_VENDOR, val8);

  // Third Vendor Capability is at 0xD0.
  ASSERT_OK(pci().GetNextCapability(PCI_CAP_ID_VENDOR, offsetB, &offsetA));
  ASSERT_EQ(0xD0, offsetA);
  ASSERT_OK(pci().ConfigRead8(offsetA, &val8));
  ASSERT_EQ(PCI_CAP_ID_VENDOR, val8);

  // Fourth Vendor Capability is at 0xE8.
  ASSERT_OK(pci().GetNextCapability(PCI_CAP_ID_VENDOR, offsetA, &offsetB));
  ASSERT_EQ(0xE8, offsetB);
  ASSERT_OK(pci().ConfigRead8(offsetB, &val8));
  ASSERT_EQ(PCI_CAP_ID_VENDOR, val8);

  // There is no fifth Vendor Capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND, pci().GetNextCapability(PCI_CAP_ID_VENDOR, offsetB, &offsetA));

  // There is an MSIX capability at 0xF8
  ASSERT_OK(pci().GetFirstCapability(PCI_CAP_ID_MSIX, &offsetA));
  ASSERT_EQ(0xF0, offsetA);
  ASSERT_OK(pci().ConfigRead8(offsetA, &val8));
  ASSERT_EQ(PCI_CAP_ID_MSIX, val8);
}

TEST_F(PciProtocolTests, GetExtendedCapabilities) {
  uint16_t offsetA = 0;
  uint16_t offsetB = 0;
  uint16_t val16 = 0;

  // First extneded capability is Virtual Channel @ 0x100
  ASSERT_OK(pci().GetFirstExtendedCapability(PCI_EXT_CAP_ID_VIRTUAL_CHANNEL_NO_MFVC, &offsetA));
  ASSERT_EQ(0x100, offsetA);
  ASSERT_OK(pci().ConfigRead16(offsetA, &val16));
  ASSERT_EQ(PCI_EXT_CAP_ID_VIRTUAL_CHANNEL_NO_MFVC, val16);

  // There is no second Virtual Channel extended capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND,
            pci().GetNextExtendedCapability(PCI_EXT_CAP_ID_VIRTUAL_CHANNEL, offsetA, &offsetB));

  // Latency Tolerance Reporting @ 0x250.
  ASSERT_OK(pci().GetFirstExtendedCapability(PCI_EXT_CAP_ID_LATENCY_TOLERANCE_REPORTING, &offsetA));
  ASSERT_EQ(0x250, offsetA);
  ASSERT_OK(pci().ConfigRead16(offsetA, &val16));
  ASSERT_EQ(PCI_EXT_CAP_ID_LATENCY_TOLERANCE_REPORTING, val16);

  // There is no second LTR extended capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND, pci().GetNextExtendedCapability(
                                  PCI_EXT_CAP_ID_LATENCY_TOLERANCE_REPORTING, offsetA, &offsetB));

  // L1 PM Substates @ 0x258.
  ASSERT_OK(pci().GetNextExtendedCapability(PCI_EXT_CAP_ID_L1PM_SUBSTATES, offsetA, &offsetA));
  ASSERT_EQ(0x258, offsetA);
  ASSERT_OK(pci().ConfigRead16(offsetA, &val16));
  ASSERT_EQ(PCI_EXT_CAP_ID_L1PM_SUBSTATES, val16);

  // There is no second L1PM Substates extended capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND,
            pci().GetNextExtendedCapability(PCI_EXT_CAP_ID_L1PM_SUBSTATES, offsetA, &offsetB));

  // Power Budgeting @ 0x128.
  ASSERT_OK(pci().GetFirstExtendedCapability(PCI_EXT_CAP_ID_POWER_BUDGETING, &offsetA));
  ASSERT_EQ(0x128, offsetA);
  ASSERT_OK(pci().ConfigRead16(offsetA, &val16));
  ASSERT_EQ(PCI_EXT_CAP_ID_POWER_BUDGETING, val16);

  // There is no second Power Budgeting extended capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND,
            pci().GetNextExtendedCapability(PCI_EXT_CAP_ID_POWER_BUDGETING, offsetA, &offsetB));

  // Vendor Specific @ 0x128.
  ASSERT_OK(pci().GetFirstExtendedCapability(PCI_EXT_CAP_ID_VENDOR, &offsetA));
  ASSERT_EQ(0x600, offsetA);
  ASSERT_OK(pci().ConfigRead16(offsetA, &val16));
  ASSERT_EQ(PCI_EXT_CAP_ID_VENDOR, val16);

  // There is no second Vendor specific capability.
  ASSERT_EQ(ZX_ERR_NOT_FOUND,
            pci().GetNextExtendedCapability(PCI_EXT_CAP_ID_VENDOR, offsetA, &offsetB));
}

TEST_F(PciProtocolTests, GetDeviceInfo) {
  uint16_t vendor_id;
  uint16_t device_id;
  uint8_t base_class;
  uint8_t sub_class;

  uint8_t program_interface;
  uint8_t revision_id;
  uint8_t bus_id = PCI_TEST_BUS_ID;
  uint8_t dev_id = PCI_TEST_DEV_ID;
  uint8_t func_id = PCI_TEST_FUNC_ID;

  ASSERT_OK(pci().ConfigRead16(PCI_CONFIG_VENDOR_ID, &vendor_id));
  ASSERT_OK(pci().ConfigRead16(PCI_CONFIG_DEVICE_ID, &device_id));
  ASSERT_EQ(vendor_id, PCI_TEST_DRIVER_VID);
  ASSERT_EQ(device_id, PCI_TEST_DRIVER_DID);
  ASSERT_OK(pci().ConfigRead8(PCI_CONFIG_CLASS_CODE_BASE, &base_class));
  ASSERT_OK(pci().ConfigRead8(PCI_CONFIG_CLASS_CODE_SUB, &sub_class));
  ASSERT_OK(pci().ConfigRead8(PCI_CONFIG_CLASS_CODE_INTR, &program_interface));
  ASSERT_OK(pci().ConfigRead8(PCI_CONFIG_REVISION_ID, &revision_id));

  zx_pcie_device_info_t info;
  ASSERT_OK(pci().GetDeviceInfo(&info));
  ASSERT_EQ(vendor_id, info.vendor_id);
  ASSERT_EQ(device_id, info.device_id);
  ASSERT_EQ(base_class, info.base_class);
  ASSERT_EQ(sub_class, info.sub_class);
  ASSERT_EQ(program_interface, info.program_interface);
  ASSERT_EQ(revision_id, info.revision_id);
  ASSERT_EQ(bus_id, info.bus_id);
  ASSERT_EQ(dev_id, info.dev_id);
  ASSERT_EQ(func_id, info.func_id);
}

zx_status_t fidl_RunTests(void*, fidl_txn_t* txn) {
  auto driver = ProtocolTestDriver::GetInstance();
  auto zxt = zxtest::Runner::GetInstance();
  zxt->AddObserver(driver);
  RUN_ALL_TESTS(0, nullptr);
  return fuchsia_device_test_DeviceRunTests_reply(txn, ZX_OK, &driver->report());
}

zx_status_t ProtocolTestDriver::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
  static const fuchsia_device_test_Test_ops_t kOps = {
      .RunTests = fidl_RunTests,
  };

  return fuchsia_device_test_Test_dispatch(this, txn, msg, &kOps);
}

static zx_status_t pci_test_driver_bind(void* ctx, zx_device_t* parent) {
  return ProtocolTestDriver::Create(parent);
}

static const zx_driver_ops_t protocol_test_driver_ops = []() {
  zx_driver_ops_t ops = {};
  ops.version = DRIVER_OPS_VERSION;
  ops.bind = pci_test_driver_bind;
  return ops;
}();

// clang-format off
ZIRCON_DRIVER_BEGIN(pci_protocol_test_driver, protocol_test_driver_ops, "zircon", "0.1", 3)
    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI),
    BI_ABORT_IF(NE, BIND_PCI_VID, PCI_TEST_DRIVER_VID),
    BI_MATCH_IF(EQ, BIND_PCI_DID, PCI_TEST_DRIVER_DID),
ZIRCON_DRIVER_END(pci_protocol_test_driver)
